import sys
import os
import re
import json
import asyncio
import httpx
import time
import subprocess
import platform
import logging
import tempfile
import win32clipboard
import win32con
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, WebDriverException
from qqmusic_api import Credential, search
import qqmusic_api.song as song_module
from qqmusic_api.song import SongFileType
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
                             QLabel, QLineEdit, QListWidget, QListWidgetItem, QComboBox, QProgressBar,
                             QMessageBox, QFileDialog, QDialog, QFormLayout, QInputDialog, QGroupBox,
                             QCheckBox)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject
from PyQt5.QtGui import QIcon

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 自动获取Cookies的线程
class AutoCookieThread(QThread):
    finished = pyqtSignal(dict)
    error = pyqtSignal(str)
    show_message = pyqtSignal(str, str)  # 添加信号用于显示消息框
    def run(self):
        try:
            # 创建临时文件用于存储Cookies
            temp_dir = tempfile.gettempdir()
            temp_file = os.path.join(temp_dir, f"qqmusic_cookies_{int(time.time())}.txt")
            logger.info("创建临时文件: %s", temp_file)
            
            # 运行内置的获取Cookies函数
            cookies = self.get_qqmusic_cookies(temp_file)
            
            if not cookies:
                self.error.emit("无法获取Cookies")
                return
            
            # 提取uin和qqmusic_key
            credentials = self.parse_cookies(cookies)
            
            if credentials:
                self.finished.emit(credentials)
            else:
                self.error.emit("无法解析Cookies")
                
        except Exception as e:
            self.error.emit(f"自动获取凭证出错: {str(e)}")
            logger.exception("自动获取凭证出错:")
    
    def get_qqmusic_cookies(self, output_file=None):
        """内置的获取QQ音乐Cookies函数"""
        logger.info("开始获取QQ音乐Cookies...")
        
        driver = None
        cookies_str = None
        
        try:
            # 设置Chrome选项 - 提高兼容性
            options = webdriver.ChromeOptions()
            options.add_argument("--disable-extensions")
            options.add_argument("--disable-gpu")
            options.add_argument("--no-sandbox")
            options.add_argument("--disable-dev-shm-usage")  # 防止共享内存不足错误
            options.add_argument("--start-maximized")  # 最大化窗口，更易操作
            options.add_argument("--ignore-certificate-errors")  # 忽略证书错误
            
            # 初始化浏览器
            driver = webdriver.Chrome(options=options)
            
            # 打开QQ音乐首页
            driver.get("https://y.qq.com/")
            logger.info("已打开QQ音乐首页")
            
            # 显示用户提示
            self.show_browser_message(driver, "QQ音乐Cookies获取工具", 
                "请使用QQ登录QQ音乐网站\n"
                "1. 点击右上角的'登录'按钮\n"
                "2. 使用QQ账号登录（推荐扫码登录）\n"
                "3. 登录成功后，右上角的'登录'按钮会变成您的头像\n"
                "4. 程序会自动检测登录状态"
            )
            
            try:
                # 使用新的登录状态检测方法
                WebDriverWait(driver, 180).until(
                    EC.presence_of_element_located((By.CSS_SELECTOR, "a.top_login__link[href*='profile']"))
                )
                logger.info("登录成功检测！正在获取Cookies...")
            except TimeoutException:
                logger.warning("未自动检测到登录状态，尝试其他方法...")
                
                # 手动确认检测
                self.show_browser_message(driver, "登录确认", "请确认您已完成登录？")
                time.sleep(2)
            
            # 获取Cookies
            cookies = driver.get_cookies()
            
            # 筛选关键Cookies - 只需要uin和qqmusic_key
            qq_cookies = []
            for c in cookies:
                if "uin" in c["name"].lower() or "qqmusic_key" in c["name"].lower():
                    qq_cookies.append(c)
            
            if not qq_cookies:
                logger.warning("未找到关键Cookies，尝试获取所有Cookies...")
                # 尝试获取其他可能的Cookie名称
                for c in cookies:
                    if "music" in c["name"].lower() or "key" in c["name"].lower():
                        qq_cookies.append(c)
                
                if not qq_cookies:
                    qq_cookies = cookies[:2]  # 只取前2个避免过多
            
            # 格式化为字符串 - 只包含uin和qqmusic_key
            cookie_string = ""
            for c in qq_cookies:
                if "uin" in c["name"].lower() or "qqmusic_key" in c["name"].lower():
                    cookie_string += f"{c['name']}={c['value']}; "
            
            # 确保有有效的Cookies
            if not cookie_string:
                logger.error("无法获取有效的Cookies")
                return None
            
            logger.info("获取到Cookies: %s", cookie_string.strip())
            
            # 保存到文件（如果指定了输出文件）
            if output_file:
                try:
                    with open(output_file, 'w', encoding='utf-8') as f:
                        f.write(cookie_string.strip())
                    logger.info("Cookies已保存到文件: %s", output_file)
                except Exception as e:
                    logger.error("保存Cookies到文件失败: %s", str(e))
            
            return cookie_string.strip()
            
        except WebDriverException as e:
            logger.error("浏览器错误: %s", str(e))
            return None
        except Exception as e:
            logger.error("程序错误: %s", str(e))
            return None
        finally:
            try:
                if driver:
                    driver.quit()
                    logger.info("浏览器已关闭")
            except:
                pass
    
    def show_browser_message(self, driver, title, message):
        """在浏览器中显示消息（使用JavaScript）"""
        try:
            # 在浏览器中执行JavaScript显示消息
            script = f"""
                var div = document.createElement('div');
                div.style.position = 'fixed';
                div.style.top = '0';
                div.style.left = '0';
                div.style.width = '100%';
                div.style.backgroundColor = '#f8f9fa';
                div.style.padding = '10px';
                div.style.zIndex = '9999';
                div.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
                div.style.textAlign = 'center';
                div.style.fontFamily = 'Arial, sans-serif';
                
                var h2 = document.createElement('h2');
                h2.textContent = '{title}';
                h2.style.margin = '0 0 5px 0';
                h2.style.color = '#343a40';
                
                var p = document.createElement('p');
                p.textContent = '{message}';
                p.style.margin = '0';
                p.style.whiteSpace = 'pre-line';
                
                div.appendChild(h2);
                div.appendChild(p);
                document.body.appendChild(div);
                
                // 5秒后自动消失
                setTimeout(function() {{
                    div.style.opacity = '0';
                    setTimeout(function() {{
                        div.remove();
                    }}, 1000);
                }}, 5000);
            """
            driver.execute_script(script)
        except:
            pass
    
    def parse_cookies(self, cookies_str):
        """解析Cookies字符串为凭证字典"""
        credentials = {}
        for cookie in cookies_str.split(';'):
            cookie = cookie.strip()
            if not cookie:
                continue
                
            parts = cookie.split('=', 1)
            if len(parts) < 2:
                continue
                
            key, value = parts[0].strip(), parts[1].strip()
            
            if key == "uin":
                # 去掉可能的"o"前缀
                if value.startswith("o"):
                    value = value[1:]
                credentials["musicid"] = value
            elif key == "qqmusic_key":
                credentials["musickey"] = value
        
        # 确保两个值都存在
        if "musicid" in credentials and "musickey" in credentials:
            logger.info("解析的凭证: musicid=%s", credentials['musicid'])
            return credentials
        
        logger.warning("无法解析Cookies: %s", cookies_str)
        return None

# 音乐下载器主窗口
class MusicDownloaderApp(QMainWindow):
    def __init__(self):
        super().__init__()
        
        # 配置参数
        self.DOWNLOAD_FOLDER = "downloads"
        self.CREDENTIAL_FILE = "qqmusic_credential.json"
        self.NUM_PER_PAGE = 10  # 每页显示数量
        
        # 凭证信息（使用字典形式存储）
        self.credential_data = {}
        
        # 搜索结果缓存
        self.search_results = []
        self.current_page = 0
        self.total_pages = 1  # 默认为1页
        
        # 当前搜索关键词
        self.current_keyword = ""
        
        # 当前下载信息
        self.current_download = None
        
        self.init_ui()
        self.load_credentials()
    
    def init_ui(self):
        self.setWindowTitle("QQ音乐下载器")
        self.setWindowIcon(QIcon("icon.ico"))
        self.setGeometry(100, 100, 800, 600)
        
        # 主布局
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)
        
        # 标题
        title_label = QLabel("QQ音乐下载器")
        title_label.setStyleSheet("font-size: 18px; font-weight: bold;")
        title_label.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(title_label)
        
        # 分隔线
        main_layout.addWidget(QLabel(""))
        
        # 凭证状态
        self.credential_status = QLabel("凭证状态: 未设置")
        main_layout.addWidget(self.credential_status)
        
        # 凭证管理按钮
        credential_layout = QHBoxLayout()
        
        self.set_credential_btn = QPushButton("设置凭证")
        self.set_credential_btn.clicked.connect(self.set_credentials)
        credential_layout.addWidget(self.set_credential_btn)
        
        self.clear_credential_btn = QPushButton("清除凭证")
        self.clear_credential_btn.clicked.connect(self.clear_credentials)
        credential_layout.addWidget(self.clear_credential_btn)
        
        self.auto_get_btn = QPushButton("自动获取凭证")
        self.auto_get_btn.clicked.connect(self.auto_get_credentials)
        credential_layout.addWidget(self.auto_get_btn)
        
        main_layout.addLayout(credential_layout)
        
        # 分隔线
        main_layout.addWidget(QLabel(""))
        
        # 搜索区域
        search_group = QGroupBox("搜索歌曲")
        search_layout = QVBoxLayout()
        
        search_form = QHBoxLayout()
        
        self.search_input = QLineEdit()
        self.search_input.setPlaceholderText("输入歌曲名、歌手或专辑")
        search_form.addWidget(self.search_input)
        
        self.search_button = QPushButton("搜索")
        self.search_button.clicked.connect(self.search_songs)
        search_form.addWidget(self.search_button)
        
        search_layout.addLayout(search_form)
        
        # 分页控件
        page_layout = QHBoxLayout()
        
        self.prev_page_btn = QPushButton("上一页")
        self.prev_page_btn.clicked.connect(self.prev_page)
        self.prev_page_btn.setEnabled(False)
        page_layout.addWidget(self.prev_page_btn)
        
        self.page_label = QLabel("第 1 页")
        page_layout.addWidget(self.page_label)
        
        self.next_page_btn = QPushButton("下一页")
        self.next_page_btn.clicked.connect(self.next_page)
        self.next_page_btn.setEnabled(False)
        page_layout.addWidget(self.next_page_btn)
        
        self.page_layout = page_layout
        search_layout.addLayout(page_layout)
        
        search_group.setLayout(search_layout)
        main_layout.addWidget(search_group)
        
        # 搜索结果列表
        self.song_list = QListWidget()
        self.song_list.itemDoubleClicked.connect(self.on_song_selected)
        main_layout.addWidget(self.song_list)
        
        # 下载信息
        download_group = QGroupBox("下载信息")
        download_layout = QVBoxLayout()
        
        self.download_info = QLabel("请先选择一首歌曲")
        download_layout.addWidget(self.download_info)
        
        # 格式选择
        self.format_label = QLabel("请选择下载格式:")
        download_layout.addWidget(self.format_label)
        
        self.format_combo = QComboBox()
        download_layout.addWidget(self.format_combo)
        
        # 下载按钮
        self.download_btn = QPushButton("下载歌曲")
        self.download_btn.clicked.connect(self.download_selected_song)
        self.download_btn.setEnabled(False)
        download_layout.addWidget(self.download_btn)
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 100)
        self.progress_bar.setVisible(False)
        download_layout.addWidget(self.progress_bar)
        
        download_group.setLayout(download_layout)
        main_layout.addWidget(download_group)
        
        # 状态栏
        self.status_bar = self.statusBar()
        self.status_bar.showMessage("就绪")
        
        # 菜单栏
        menu_bar = self.menuBar()
        
        file_menu = menu_bar.addMenu("文件")
        
        save_credential_action = file_menu.addAction("保存凭证")
        save_credential_action.triggered.connect(self.save_credential)
        
        change_folder_action = file_menu.addAction("更改下载目录")
        change_folder_action.triggered.connect(self.change_download_folder)
        
        exit_action = file_menu.addAction("退出")
        exit_action.triggered.connect(self.close)
        
        help_menu = menu_bar.addMenu("帮助")
        
        about_action = help_menu.addAction("关于")
        about_action.triggered.connect(self.show_about)
    
    def set_credentials(self):
        """设置凭证"""
        if self.get_manual_credentials():
            self.status_bar.showMessage("凭证已设置")
            self.credential_status.setText("凭证状态: 已设置")
    
    def clear_credentials(self):
        """清除凭证"""
        self.credential_data = {}
        
        # 删除凭证文件
        if os.path.exists(self.CREDENTIAL_FILE):
            try:
                os.remove(self.CREDENTIAL_FILE)
                self.status_bar.showMessage("凭证文件已删除")
                logger.info("凭证文件已删除")
            except Exception as e:
                QMessageBox.warning(self, "删除凭证", f"删除凭证文件失败: {e}")
                logger.error("删除凭证文件失败: %s", e)
        
        self.credential_status.setText("凭证状态: 未设置")
        QMessageBox.information(self, "凭证清除", "凭证信息已清除")
    
    def auto_get_credentials(self):
        """自动获取凭证"""
        self.status_bar.showMessage("正在尝试自动获取凭证...")
        QApplication.processEvents()  # 更新UI
        
        # 创建并启动自动获取凭证线程
        self.cookie_thread = AutoCookieThread()
        self.cookie_thread.finished.connect(self.on_auto_cookie_success)
        self.cookie_thread.error.connect(self.on_auto_cookie_error)
        self.cookie_thread.start()
    
    def on_auto_cookie_success(self, credentials):
        """自动获取凭证成功"""
        self.credential_data = credentials
        self.save_credential()
        self.credential_status.setText("凭证状态: 已自动获取")
        self.status_bar.showMessage("凭证已自动获取并保存")
        QMessageBox.information(self, "自动获取凭证", "凭证已成功获取并保存！")
        logger.info("自动获取凭证成功: musicid=%s", credentials['musicid'])
    
    def on_auto_cookie_error(self, error):
        """自动获取凭证失败"""
        self.status_bar.showMessage("自动获取凭证失败")
        logger.error("自动获取凭证失败: %s", error)
        
        # 提示用户手动输入
        reply = QMessageBox.question(
            self, 
            "自动获取凭证失败", 
            f"自动获取凭证失败: {error}\n\n是否手动输入凭证？",
            QMessageBox.Yes | QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            self.get_manual_credentials()
    
    def get_manual_credentials(self):
        """手动获取凭证"""
        dialog = CredentialDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            credentials = dialog.get_credentials()
            self.credential_data = {
                "musicid": credentials["musicid"],
                "musickey": credentials["musickey"]
            }
            
            if self.credential_data.get("musicid") and self.credential_data.get("musickey"):
                if credentials["save"]:
                    self.save_credential()
                logger.info("手动设置凭证: musicid=%s", self.credential_data['musicid'])
                return True
        return False
    
    def save_credential(self):
        """保存凭证到文件"""
        if not self.credential_data.get('musicid') or not self.credential_data.get('musickey'):
            QMessageBox.warning(self, "保存凭证", "无法保存凭证，凭证信息不完整")
            return
            
        try:
            with open(self.CREDENTIAL_FILE, "w") as f:
                json.dump(self.credential_data, f)
            self.status_bar.showMessage("凭证已保存")
            self.credential_status.setText("凭证状态: 已设置并保存")
            logger.info("凭证已保存: musicid=%s", self.credential_data['musicid'])
        except Exception as e:
            QMessageBox.critical(self, "保存凭证错误", f"保存凭证失败: {e}")
            logger.error("保存凭证失败: %s", e)
    
    def load_credentials(self):
        """从文件加载凭证"""
        try:
            if os.path.exists(self.CREDENTIAL_FILE):
                with open(self.CREDENTIAL_FILE, "r") as f:
                    self.credential_data = json.load(f)
                if self.credential_data.get("musicid") and self.credential_data.get("musickey"):
                    self.status_bar.showMessage("已加载保存的凭证")
                    self.credential_status.setText("凭证状态: 已加载")
                    logger.info("已加载凭证: musicid=%s", self.credential_data['musicid'])
                    return True
            # 如果没有凭证文件，尝试自动获取
            self.auto_get_credentials()
            return False
        except Exception as e:
            QMessageBox.critical(self, "加载凭证错误", f"加载凭证失败: {e}")
            self.credential_status.setText("凭证状态: 错误")
            logger.error("加载凭证失败: %s", e)
            return False
    
    def search_songs(self):
        """搜索歌曲"""
        self.current_keyword = self.search_input.text().strip()
        if not self.current_keyword:
            QMessageBox.warning(self, "搜索", "请输入搜索关键词")
            return
            
        # 如果凭证不存在，提示用户输入
        if not self.credential_data:
            if self.get_manual_credentials():
                QMessageBox.information(self, "凭证", "凭证已设置")
            else:
                QMessageBox.information(self, "凭证", "使用游客模式登录（部分功能可能受限）")
                logger.info("使用游客模式搜索")
        
        # 重置页码
        self.current_page = 0
        self.perform_search()
    
    def prev_page(self):
        """上一页"""
        if self.current_page > 0:
            self.current_page -= 1
            self.perform_search()
    
    def next_page(self):
        """下一页"""
        # 检查是否有更多结果
        self.current_page += 1
        self.perform_search()
    
    def perform_search(self):
        """执行搜索"""
        if not self.current_keyword:
            return
            
        # 禁用界面
        self.search_button.setEnabled(False)
        self.status_bar.showMessage("正在搜索...")
        logger.info("搜索关键词: %s (第 %d 页)", self.current_keyword, self.current_page)
        
        # 创建异步任务
        async def search_task():
            # 在搜索中也使用凭证
            cred = None
            if self.credential_data and self.credential_data.get('musicid') and self.credential_data.get('musickey'):
                cred = Credential(
                    musicid=self.credential_data['musicid'],
                    musickey=self.credential_data['musickey']
                )
            
            # 执行搜索
            result = await search.search_by_type(
                keyword=self.current_keyword, 
                num=self.NUM_PER_PAGE,
                page=self.current_page + 1,  # API页码从1开始
                credential=cred  # 使用凭证
            )
            return result
        
        self.async_thread = AsyncThread(search_task())
        self.async_thread.finished.connect(self.on_search_completed)
        self.async_thread.error.connect(self.on_search_error)
        self.async_thread.start()
    
    def on_search_completed(self, result):
        """搜索完成处理"""
        self.search_button.setEnabled(True)
        
        # 更新搜索结果
        self.search_results = result if isinstance(result, list) else []
        status = f"找到 {len(self.search_results)} 首歌曲"
        self.status_bar.showMessage(status)
        logger.info(status)
        
        # 更新分页控件
        self.page_label.setText(f"第 {self.current_page + 1} 页")
        self.prev_page_btn.setEnabled(self.current_page > 0)
        self.next_page_btn.setEnabled(True)  # 允许尝试下一页
        
        # 显示搜索结果
        self.display_search_results()
    
    def on_search_error(self, error):
        """搜索错误处理"""
        self.search_button.setEnabled(True)
        error_msg = f"搜索失败: {error}"
        self.status_bar.showMessage(error_msg)
        QMessageBox.critical(self, "搜索错误", error_msg)
        logger.error(error_msg)
    
    def display_search_results(self):
        """显示搜索结果"""
        self.song_list.clear()
        
        # 添加结果到列表
        for song_data in self.search_results:
            title = song_data.get("title", "未知歌曲")
            singers = ", ".join([s["name"] for s in song_data.get("singer", [])]) if song_data.get("singer") else "未知歌手"
            album = song_data.get("album", {}).get("name", "未知专辑")
            
            item = QListWidgetItem(f"{title} - {singers} [专辑: {album}]")
            item.setData(Qt.UserRole, song_data)  # 存储原始数据
            self.song_list.addItem(item)
    
    def on_song_selected(self, item):
        """歌曲选择处理"""
        song_data = item.data(Qt.UserRole)
        self.selected_song = song_data
        
        # 获取可用格式
        formats = self.get_available_formats(song_data)
        
        # 更新下载信息
        title = song_data.get("title", "未知歌曲")
        singers = ", ".join([s["name"] for s in song_data.get("singer", [])]) if song_data.get("singer") else "未知歌手"
        
        self.download_info.setText(f"已选择: {title} - {singers}")
        
        # 更新格式选择
        self.format_combo.clear()
        if formats:
            for size_key, name in formats:
                self.format_combo.addItem(name, size_key)
        else:
            self.format_combo.addItem("MP3高品质(320kbps)", "size_320mp3")
        
        self.format_label.setVisible(len(formats) > 0)
        self.format_combo.setVisible(len(formats) > 0)
        self.download_btn.setEnabled(True)
    
    def get_available_formats(self, song_data):
        """获取歌曲支持的格式列表"""
        if not song_data:
            return []
            
        file_info = song_data.get("file", {})
        
        formats = []
        
        # 特殊处理 size_new 数组
        size_new = file_info.get("size_new", [])
        if isinstance(size_new, list) and len(size_new) > 0:
            for i, size_value in enumerate(size_new):
                if isinstance(size_value, int) and size_value > 0:
                    if i in [0, 1, 2, 5]:
                        key = f"size_new_{i}"
                        formats.append((key, self.get_file_type_name(key)))
        
        # 检查支持的常规格式
        for size_key, size_value in file_info.items():
            if isinstance(size_value, int) and size_value > 0:
                # 跳过已处理的size_new和非格式键
                if size_key.startswith("size_new") or not size_key.startswith("size_"):
                    continue
                    
                format_name = self.get_file_type_name(size_key)
                if format_name != "未知格式":
                    formats.append((size_key, format_name))
        
        # 排序：高品质格式在前
        priority = ["FLAC无损", "Hi-Res高解析音频", "臻品母带2.0", 
                   "臻品全景声2.0", "臻品音质2.0", "MP3高品质(320kbps)",
                   "OGG高品质(640kbps)", "AAC高品质(192kbps)"]
        
        def sort_key(item):
            name = item[1]
            try:
                return priority.index(name)
            except ValueError:
                return len(priority)
        
        return sorted(formats, key=sort_key)
    
    def get_file_type_name(self, size_key: str) -> str:
        """根据文件大小键名获取文件类型名称"""
        size_mapping = {
            "size_flac": "FLAC无损",
            "size_320mp3": "MP3高品质(320kbps)",
            "size_192aac": "AAC高品质(192kbps)",
            "size_128mp3": "MP3标准(128kbps)",
            "size_96aac": "AAC标准(96kbps)",
            "size_48aac": "AAC低品质(48kbps)",
            "size_hires": "Hi-Res高解析音频",
            "size_master": "臻品母带",
            "size_new_0": "臻品母带2.0",
            "size_new_1": "臻品全景声2.0",
            "size_new_2": "臻品音质2.0",
            "size_new_5": "OGG高品质(640kbps)"
        }
        return size_mapping.get(size_key, "未知格式")
    
    def download_selected_song(self):
        """下载选中的歌曲"""
        if not hasattr(self, 'selected_song'):
            QMessageBox.warning(self, "下载", "请先选择一首歌曲")
            return
            
        song_data = self.selected_song
        mid = song_data.get("mid")
        
        if not mid:
            QMessageBox.critical(self, "下载错误", "歌曲缺少MID")
            return
            
        title = song_data.get("title", "未知歌曲")
        singer_names = [s["name"] for s in song_data.get("singer", [])]
        singer = "&".join(singer_names) if singer_names else "未知歌手"
        
        # 创建安全的文件名
        filename = self.sanitize_filename(f"{title}-{singer}")
        
        # 获取格式信息
        if self.format_combo.currentIndex() >= 0:
            size_key = self.format_combo.currentData()
        else:
            size_key = "size_320mp3"
        
        # 映射格式键到文件类型
        file_type_mapping = {
            "size_flac": SongFileType.FLAC,
            "size_320mp3": SongFileType.MP3_320,
            "size_192aac": SongFileType.ACC_192,
            "size_128mp3": SongFileType.MP3_128,
            "size_96aac": SongFileType.ACC_96,
            "size_48aac": SongFileType.ACC_48,
            "size_new_0": SongFileType.MASTER,
            "size_new_1": SongFileType.ATMOS_2,
            "size_new_2": SongFileType.ATMOS_51,
            "size_new_5": SongFileType.OGG_640,
        }
        
        file_type = file_type_mapping.get(size_key, SongFileType.MP3_320)
        
        # 创建下载线程
        logger.info("开始下载歌曲: title=%s, singer=%s, format=%s", title, singer, size_key)
        self.download_thread = DownloadThread(
            mid=mid,
            file_type=file_type,
            credential_dict=self.credential_data,  # 传入凭证数据
            filename=filename,
            download_folder=self.DOWNLOAD_FOLDER
        )
        
        # 连接信号
        self.download_thread.progress.connect(self.update_download_progress)
        self.download_thread.finished.connect(self.on_download_complete)
        self.download_thread.error.connect(self.on_download_error)
        
        # 准备UI
        self.progress_bar.setVisible(True)
        self.progress_bar.setValue(0)
        self.download_btn.setEnabled(False)
        self.status_bar.showMessage("下载中...")
        
        # 开始下载
        self.download_thread.start()
    
    def sanitize_filename(self, filename: str) -> str:
        """清除文件名中的非法字符"""
        illegal_chars = r'[<>:"/\\|?*\x00-\x1f]'
        sanitized = re.sub(illegal_chars, " ", filename)
        sanitized = sanitized.strip()
        if len(sanitized) > 50:
            sanitized = sanitized[:50] + "..."
        return sanitized
    
    def update_download_progress(self, percent):
        """更新下载进度"""
        self.progress_bar.setValue(percent)
    
    def on_download_complete(self, file_path):
        """下载完成处理"""
        self.progress_bar.setVisible(False)
        self.download_btn.setEnabled(True)
        self.status_bar.showMessage("下载完成")
        logger.info("下载完成: %s", file_path)
        
        # 询问是否打开文件所在位置
        reply = QMessageBox.question(
            self,
            "下载完成",
            f"文件已保存到:\n{file_path}\n\n是否打开所在文件夹?",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.Yes
        )
        
        if reply == QMessageBox.Yes:
            # 打开文件所在文件夹
            os.startfile(os.path.dirname(file_path))
    
    def on_download_error(self, error):
        """下载错误处理"""
        self.progress_bar.setVisible(False)
        self.download_btn.setEnabled(True)
        error_msg = f"下载失败: {error}"
        self.status_bar.showMessage(error_msg)
        QMessageBox.critical(self, "下载错误", error_msg)
        logger.error(error_msg)
    
    def change_download_folder(self):
        """更改下载目录"""
        folder = QFileDialog.getExistingDirectory(self, "选择下载目录", self.DOWNLOAD_FOLDER)
        if folder:
            self.DOWNLOAD_FOLDER = folder
            status = f"下载目录已更新为: {folder}"
            self.status_bar.showMessage(status)
            logger.info(status)
    
    def show_about(self):
        """显示关于对话框"""
        QMessageBox.about(self, "关于QQ音乐下载器", 
            "QQ音乐下载器 v1.0\n\n"
            "功能：\n"
            "- 搜索QQ音乐曲库\n"
            "- 下载高品质歌曲\n"
            "- 支持多种音频格式\n"
            "- 自动获取登录凭证\n"
            "- 仅供学习交流\n\n"
            "开发人员: 小海\n"
            "发布日期: 2025"
        )
    
    def closeEvent(self, event):
        """关闭应用时确保所有线程结束"""
        if hasattr(self, 'download_thread') and self.download_thread.isRunning():
            self.download_thread.terminate()
            self.download_thread.wait()
        event.accept()

# 异步任务处理线程
class AsyncThread(QThread):
    finished = pyqtSignal(object)
    error = pyqtSignal(str)
    progress = pyqtSignal(int)
    
    def __init__(self, coroutine):
        super().__init__()
        self.coroutine = coroutine
    
    def run(self):
        try:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            result = loop.run_until_complete(self.coroutine)
            self.finished.emit(result)
        except Exception as e:
            self.error.emit(str(e))
            logger.exception("AsyncThread error:")

# 下载任务处理线程
class DownloadThread(QThread):
    progress = pyqtSignal(int)
    finished = pyqtSignal(str)
    error = pyqtSignal(str)
    
    def __init__(self, mid, file_type, credential_dict, filename, download_folder):
        super().__init__()
        self.mid = mid
        self.file_type = file_type
        self.credential_dict = credential_dict  # 传入凭证字典
        self.filename = filename
        self.download_folder = download_folder
        
        # 延迟创建凭证对象，确保在正确的线程中使用
        self.credential = None
    
    def run(self):
        try:
            # 检查凭证字典是否有效
            if self.credential_dict and 'musicid' in self.credential_dict and 'musickey' in self.credential_dict:
                self.credential = Credential(
                    musicid=self.credential_dict['musicid'],
                    musickey=self.credential_dict['musickey']
                )
                logger.info("使用凭证下载: musicid=%s", self.credential_dict['musicid'])
            else:
                self.credential = Credential()
                logger.info("使用游客凭证下载")
            
            # 创建下载目录
            os.makedirs(self.download_folder, exist_ok=True)
            
            # 使用与凭证相同的环境获取URL
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            urls = loop.run_until_complete(
                song_module.get_song_urls(
                    mid=[self.mid],
                    file_type=self.file_type,
                    credential=self.credential
                )
            )
            
            if not urls or self.mid not in urls:
                self.error.emit("无法获取歌曲下载链接")
                logger.warning("无法获取歌曲下载链接")
                return
                
            url_info = urls[self.mid]
            url = url_info[0] if isinstance(url_info, tuple) else url_info
            logger.info("获取到下载URL: %s", url)
                
            # 获取扩展名
            ext = self.get_file_extension(self.file_type)
            file_path = f"{self.download_folder}/{self.filename}{ext}"
            
            # 避免文件覆盖
            temp_file_path = file_path
            counter = 1
            while os.path.exists(temp_file_path):
                temp_file_path = f"{self.download_folder}/{self.filename}_{counter}{ext}"
                counter += 1
            
            file_path = temp_file_path
            
            # 执行下载
            with httpx.Client() as client:
                with client.stream("GET", url) as response:
                    if response.status_code != 200:
                        self.error.emit(f"HTTP错误: {response.status_code}")
                        return
                    
                    total_size = int(response.headers.get("Content-Length", 0))
                    downloaded = 0
                    
                    with open(file_path, "wb") as f:
                        for chunk in response.iter_bytes(1024 * 10):
                            if chunk:
                                f.write(chunk)
                                downloaded += len(chunk)
                                
                                # 更新进度
                                if total_size > 0:
                                    percent = min(100, int(downloaded / total_size * 100))
                                    self.progress.emit(percent)
            
            self.finished.emit(file_path)
            
        except Exception as e:
            self.error.emit(str(e))
            logger.exception("下载过程中发生错误:")
    
    def get_file_extension(self, file_type):
        # 根据文件类型获取扩展名
        extension_mapping = {
            SongFileType.FLAC: ".flac",
            SongFileType.MP3_320: ".mp3",
            SongFileType.ACC_192: ".m4a",
            SongFileType.MP3_128: ".mp3",
            SongFileType.ACC_96: ".m4a",
            SongFileType.ACC_48: ".m4a",
            SongFileType.MASTER: ".flac",
            SongFileType.ATMOS_2: ".flac",
            SongFileType.ATMOS_51: ".flac",
            SongFileType.OGG_640: ".ogg",
        }
        return extension_mapping.get(file_type, ".mp3")

# 凭证输入对话框
class CredentialDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("QQ音乐凭证输入")
        self.setWindowIcon(QIcon("icon.ico"))
        self.setGeometry(300, 300, 400, 200)
        
        layout = QVBoxLayout()
        
        # 说明文本
        info_label = QLabel(
            "1. 在浏览器中登录QQ音乐\n"
            "2. 按F12打开开发者工具\n"
            "3. 切换到Application -> Storage -> Cookies\n"
            "4. 找到uin(对应MUSICID)和qqmusic_key(对应MUSICKEY)\n"
            "5. 将值填入下方"
        )
        layout.addWidget(info_label)
        
        # 表单布局
        form_layout = QFormLayout()
        
        self.musicid_input = QLineEdit()
        self.musickey_input = QLineEdit()
        
        form_layout.addRow("MUSICID:", self.musicid_input)
        form_layout.addRow("MUSICKEY:", self.musickey_input)
        
        layout.addLayout(form_layout)
        
        # 保存凭证选择
        self.save_credential_cb = QCheckBox("保存凭证以便下次使用")
        self.save_credential_cb.setChecked(True)
        layout.addWidget(self.save_credential_cb)
        
        # 按钮
        button_layout = QHBoxLayout()
        
        self.ok_button = QPushButton("确定")
        self.ok_button.clicked.connect(self.accept)
        
        self.cancel_button = QPushButton("取消")
        self.cancel_button.clicked.connect(self.reject)
        
        button_layout.addWidget(self.ok_button)
        button_layout.addWidget(self.cancel_button)
        
        layout.addLayout(button_layout)
        
        self.setLayout(layout)
    
    def get_credentials(self):
        return {
            "musicid": self.musicid_input.text().strip(),
            "musickey": self.musickey_input.text().strip(),
            "save": self.save_credential_cb.isChecked()
        }

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle("Fusion")  # 使用更现代的样式
    window = MusicDownloaderApp()
    window.show()
    sys.exit(app.exec_())